﻿using Sidvall.Net;
using Sidvall.Serialization;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.Security
{
    public static class SecurityManager
    {
        #region Public Members

        public static event System.EventHandler<TokenResponseEventArgs> AuthenticationHeaderChanged;

        #region CreateAuthorizationManagerAsync

        public static async Task<Sidvall.Security.AuthorizationManager> CreateAuthorizationManagerAsync(string factoryTypeName, string context)
        {
            var item = new Sidvall.Reflection.InstanceItem()
            {
                FactoryTypeName = factoryTypeName,
                Context = context,
            };
            var authorizationProvider = await Sidvall.SystemContext.Current.CodeManager.CreateInstanceAsync<IAuthorizationProvider>(item).ConfigureAwait(false);
            if (authorizationProvider != null)
                return new Sidvall.Security.AuthorizationManager(authorizationProvider);
            return null;
        }

        #endregion
        #region CreateServerContent

        public static ServerContent CreateServerContent(string name)
        {
            var item = new ServerContent()
            {
                Parameters = new Sidvall.Data.ParameterCollection(),
                ResourceItems = new ResourceItemCollection(),
                UserInfo = new UserInfo(),
                UserItem = new UserItem()
                {
                    UserStatus = Sidvall.Security.IdentityStatus.Valid,
                    UserStatusReasonPhrase = null,
                    Name = name,
                    Context = null,
                    CreatedTime = System.DateTime.UtcNow,
                    UpdatedTime = System.DateTime.UtcNow,
                },
            };
            item.ResourceItems.Add(null, null, null, null, null, ResourceItemAccessLevel.Public);
            item.UserInfo.SetName(name);
            return item;
        }

        #endregion
        #region GetServerContentAsync

        public static async Task<ServerContent> GetServerContentAsync(IIdentity identity, bool loadResourceItem, bool loadUserItem)
        {
            return await GetServerContentAsync(identity, null, null, loadResourceItem, false, loadUserItem, null).ConfigureAwait(false);
        }
        public static async Task<ServerContent> GetServerContentAsync(IIdentity identity, string authorityUserInfoEndpoint, string accessToken,
            bool loadResourceItem, bool loadUserInfo, bool loadUserItem)
        {
            return await GetServerContentAsync(identity, authorityUserInfoEndpoint, accessToken, loadResourceItem, loadUserInfo, loadUserItem, null).ConfigureAwait(false);
        }
        public static async Task<ServerContent> GetServerContentAsync(IIdentity identity, string authorityUserInfoEndpoint, string accessToken,
            bool loadResourceItem, bool loadUserInfo, bool loadUserItem, IServerContentProvider provider)
        {
            var item = new ServerContent();
            var tasks = new List<Task>();
            if (provider != null)
                tasks.Add(LoadParametersAsync(item, identity, provider));
            if (loadResourceItem)
                tasks.Add(LoadResourceItemsAsync(item));
            if ((loadUserInfo) && (!string.IsNullOrWhiteSpace(authorityUserInfoEndpoint)) && (!string.IsNullOrWhiteSpace(accessToken)))
                tasks.Add(LoadUserInfoAsync(item, authorityUserInfoEndpoint, accessToken));
            if (loadUserItem)
                tasks.Add(LoadUserItemAsync(item, identity));
            await Task.WhenAll(tasks).ConfigureAwait(false);
            if (provider != null)
                await provider.InitializeAsync(item, identity).ConfigureAwait(false);
            return item;
        }
        public static async Task<ServerContent> GetServerContentAsync(string address, IAuthenticationStrategy authenticationStrategy)
        {
            var settings = new HttpClientSettings();
            if (authenticationStrategy != null)
                settings.AuthenticationHeader = authenticationStrategy.CurrentAuthenticationHeader;
            var client = Sidvall.SystemContext.Current.HttpClientFactory.GetHttpClient(settings);
            return await GetServerContentAsync(string.Empty, authenticationStrategy, client).ConfigureAwait(false);
        }
        public static async Task<ServerContent> GetServerContentAsync(string address, IAuthenticationStrategy authenticationStrategy, IHttpClient client)
        {
            if (client == null)
                throw new System.ArgumentNullException(nameof(client));
            var response = await client.GetAsync(address).ConfigureAwait(false);
            if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
            {
                if (authenticationStrategy != null)
                {
                    if (await authenticationStrategy.NewAuthenticationHeaderFromRefreshTokenAsync().ConfigureAwait(false))
                    {
                        client.AuthenticationHeader = authenticationStrategy.CurrentAuthenticationHeader;
                        if (client.AuthenticationHeader != null)
                            response = await client.GetAsync(address).ConfigureAwait(false);
                    }
                }
            }
            return await response.GetContentResolvedAsync<ServerContent>().ConfigureAwait(false);
        }

        #endregion
        #region GetTokenResponseFromClientCredentialsAsync

        public static async Task<Sidvall.Security.TokenResponse> GetTokenResponseFromClientCredentialsAsync(string authorityTokenEndpoint, string clientId, string clientSecret,
            string scope, Dictionary<string, string> additionalValues, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(scope))
                return null;
            var client = Sidvall.SystemContext.Current.AuthenticationManager.GetAuthenticationClient(authorityTokenEndpoint, clientId, clientSecret);
            var tokenResponse = await client.RequestClientCredentialsAsync(scope, additionalValues, cancellationToken).ConfigureAwait(false);
            EnsureSuccess(tokenResponse);
            return tokenResponse;
        }

        #endregion
        #region GetTokenResponseFromCodeAsync

        public static async Task<Sidvall.Security.TokenResponse> GetTokenResponseFromCodeAsync(string authorityTokenEndpoint, string clientId, string clientSecret, string code,
            string redirectAddress, string codeVerifier, Dictionary<string, string> additionalValues, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(code))
                return null;
            var client = Sidvall.SystemContext.Current.AuthenticationManager.GetAuthenticationClient(authorityTokenEndpoint, clientId, clientSecret);
            var tokenResponse = await client.RequestAuthorizationCodeAsync(code, redirectAddress, codeVerifier, additionalValues, cancellationToken).ConfigureAwait(false);
            EnsureSuccess(tokenResponse);
            return tokenResponse;
        }

        #endregion
        #region GetTokenResponseFromRefreshTokenAsync

        public static async Task<Sidvall.Security.TokenResponse> GetTokenResponseFromRefreshTokenAsync(string authorityTokenEndpoint, string clientId, string clientSecret,
            string refreshToken, Dictionary<string, string> additionalValues, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(refreshToken))
                return null;
            var client = Sidvall.SystemContext.Current.AuthenticationManager.GetAuthenticationClient(authorityTokenEndpoint, clientId, clientSecret);
            var tokenResponse = await client.RequestRefreshTokenAsync(refreshToken, additionalValues, cancellationToken).ConfigureAwait(false);
            EnsureSuccess(tokenResponse);
            return tokenResponse;
        }

        #endregion
        #region GetTokenResponseFromResourceOwnerPasswordAsync

        public static async Task<Sidvall.Security.TokenResponse> GetTokenResponseFromResourceOwnerPasswordAsync(string authorityTokenEndpoint, string clientId, string clientSecret,
            string userName, string password, string scope, Dictionary<string, string> additionalValues, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(userName))
                return null;
            var client = Sidvall.SystemContext.Current.AuthenticationManager.GetAuthenticationClient(authorityTokenEndpoint, clientId, clientSecret);
            var tokenResponse = await client.RequestResourceOwnerPasswordAsync(userName, password, scope, additionalValues, cancellationToken).ConfigureAwait(false);
            EnsureSuccess(tokenResponse);
            return tokenResponse;
        }

        #endregion
        #region OnAuthenticationHeaderChanged

        public static void OnAuthenticationHeaderChanged(TokenResponse tokenResponse)
        {
            if (tokenResponse == null)
                return;
            var args = new TokenResponseEventArgs(tokenResponse);
            OnAuthenticationHeaderChanged(args);
        }
        private static void OnAuthenticationHeaderChanged(TokenResponseEventArgs args)
        {
            if (AuthenticationHeaderChanged != null)
                AuthenticationHeaderChanged.Invoke(null, args);
        }

        #endregion

        #endregion
        #region Private Members

        #region EnsureSuccess

        private static void EnsureSuccess(Sidvall.Security.TokenResponse tokenResponse)
        {
            Sidvall.Net.HttpResponse.EnsureSuccessStatusCode(tokenResponse.HttpErrorStatusCode, tokenResponse.HttpErrorReason, tokenResponse.IsHttpError);
            if (tokenResponse.IsError)
                throw new Sidvall.Net.HttpException(tokenResponse.Error);
        }

        #endregion
        #region LoadParametersAsync

        private static async Task LoadParametersAsync(ServerContent item, IIdentity identity, IServerContentProvider provider)
        {
            var values = await provider.GetParametersAsync(identity).ConfigureAwait(false);
            if (item.Parameters == null)
                item.Parameters = new Sidvall.Data.ParameterCollection();
            item.Parameters.AddRange(values);
        }

        #endregion
        #region LoadResourceItemsAsync

        private static async Task LoadResourceItemsAsync(ServerContent item)
        {
            item.ResourceItems = await Sidvall.SystemContext.Current.AuthorizationManager.GetResourceItemsAsync().ConfigureAwait(false);
        }

        #endregion
        #region LoadUserInfoAsync

        private static async Task LoadUserInfoAsync(ServerContent item, string authorityUserInfoEndpoint, string accessToken)
        {
            item.UserInfo = await Sidvall.SystemContext.Current.AuthenticationManager.GetUserInfoAsync(authorityUserInfoEndpoint, accessToken).ConfigureAwait(false);
        }

        #endregion
        #region LoadUserItemAsync

        private static async Task LoadUserItemAsync(ServerContent item, IIdentity identity)
        {
            item.UserItem = await Sidvall.SystemContext.Current.UserManager.GetUserItemAsync(identity).ConfigureAwait(false);
        }

        #endregion

        #endregion
    }
}
